package org.openzip.openzip.gui.util.fileTree;

import java.util.*;
import java.io.File;
import java.io.FilenameFilter;

import javax.swing.tree.TreeNode;

import org.openzip.openzip.gui.util.FileUtil;
import org.openzip.openzip.gui.util.checkBox.FileCheckBoxModel;

/**
 * This is an implementation of <code>TreeNode</code> that displays each
 * node as a <code>File</code> (icon and name) with a checkbox. It will load 
 * children lazily and also maintains selection hierarchy via 
 * <code>ObserverTreestateCheckBox</code>.
 * 
 * <code>FileTreeNode</code> makes use of <code>FileCheckBox</code> and could be
 * considered as <code>TreeNode</code> wrapper for it.
 * 
 * @author Jakub Závěrka
 */
public class FileTreeNode implements TreeNode 
{
	//Checkbox instance for the File represented by actual node
	private FileCheckBoxModel checkbox;
	//Parent node
	private FileTreeNode parent;
	//Collection of children nodes
	private List<FileTreeNode> children; 
	//Indicates whether children have already been loaded (Lazy loading)
	private boolean loaded;
	//Indicates whether we care only about directories when loading children
	private boolean directoriesOnly;
	
	/**
	 * Initializes a new <code>FileTreeNode</code>.
	 * 
	 * @param file
	 *           The underlying fire that will be represented by this node.
	 * @param parent
	 *           Parent <code>FileTreeNode</code>.
	 * @param directoriesOnly
	 *           If directories only should be loaded as children.
	 */
	public FileTreeNode(File file, FileTreeNode parent, boolean directoriesOnly){
		// Wrapped FileCheckBox is being created
		checkbox = new FileCheckBoxModel(file);
		this.parent = parent;
		//Children, so far empty
		children = new ArrayList<FileTreeNode>();
		
		this.directoriesOnly = directoriesOnly;
	}
	
	/**
	 * This constructor doesn't specify a parent TreeNode. Its main purpose is
	 * for creating root nodes.
	 * 
	 * @param file
	 *           File which is to be represented by this
	 *           <code>FileTreeNode</code> in the tree
	 */
	public FileTreeNode(File file, boolean directoriesOnly) {
		
		this(file, null, directoriesOnly);
		System.out.println(file.getPath() + ": entering FileTreeNode(File, directoriesOnly) constructor");
	}
	

	/** 
	 * Returns an <code>Enumeration</code> of all children.
	 */
	@Override
	public Enumeration children() {
		System.out.println(checkbox.getFile().getPath() + ": entering children() method");
		//we must load them first
		loadChildren();
		return new FileEnumeration();
	}

	/**
	 * Indicates whether node allows children. This is usually closely tied with
	 * <code>File</code> being directory/file.
	 * 
	 * @return <code>true</code>, if underlying file is a folder, false
	 *         otherwise.
	 */
	@Override
	public boolean getAllowsChildren() {
		System.out.println(checkbox.getFile().getPath() + ": entering getAllowsChildren() method");
		return checkbox.getFile().isDirectory();
	}

	/** 
	 * Returns child node at specified index 
	 */
	@Override
	public TreeNode getChildAt(int index) {
		System.out.println(checkbox.getFile().getPath() + ": entering getChildAt() method");
		//we must load them first
		loadChildren();
		return children.get(index);
	}
	
	/** 
	 * Returns the number of children for this node.
	 */
	@Override
	public int getChildCount() {
		System.out.println(checkbox.getFile().getPath() + ": entering getChildCount() method");
		//If the node is leaf, it has no children and therefore returns 0
		if(!isLeaf()){
			//If no, we must load the children first
			loadChildren();
			//and then return the number of them
			return children.size();
		}
		else return 0;
	}

	/** 
	 * Returns index (position in the children collection) of the child node 
	 * given as parameter.
	 */
	@Override
	public int getIndex(TreeNode node) {
		System.out.println(checkbox.getFile().getPath() + ": entering getIndex() method");
		//we need not to load the children, because unless they are loaded,
		//there cannot be a meaningful value in the parameter - nodes are not
		//created yet.
		loadChildren();
		return children.indexOf(node);
	}

	/** 
	 * Returns the parent node. 
	 */ 
	@Override
	public TreeNode getParent() {
	
		return parent;
	}

	/**
	 * Tells if the receiver is leaf or branch. For the directory/file context it
	 * means true for directory with subdirectories and false for file and
	 * directory without subfiles (including subdirectories).<br>
	 * <br>
	 * We are not loading the children via the loadChildren() method for 
	 * optimalizing purposes. (Moreover, we need not to load the children to
	 * determine if the node is a leaf or branch.) 
	 * @returns True, if the receiver is leaf, false is it's branch.
	 */
	@Override
	public boolean isLeaf() {
		System.out.println(checkbox.getFile().getPath() + ": entering isLeaf() method");
		//if its file, return true
		if(checkbox.getFile().isFile()){
			System.out.println("method returns: true, because of isFile()");
			return true;
		}
		//else list all the subfiles of the folder 
		String[] l = checkbox.getFile().list(new NodeFilenameFilter());
		if(l == null){
			//if there are none, return true
			System.out.println("method returns: true, because getFile().list() returned null");
			return true;
		}
		//else return whether their number is 0 or not
		System.out.println("method returns: " + (l.length == 0) + ", because file list has 0 length");
		return l.length == 0;
	}
	
	/** 
	 * Returns the <code>FileCheckBox</code> represented by this node 
	 */
	public FileCheckBoxModel getFileCheckBox() {
		System.out.println(checkbox.getFile().getPath() + ": entering getFileCheckBox() method");
		return checkbox;
	}
	
	/**
	 * This returns the last folder for the underlying file. This method
	 * is used to manually expand the tree for a given filepath.
	 */
	public String getFilePathLast(){
		System.out.println(checkbox.getFile().getPath() + ": entering getFilePathLast() method");
		String[] folders = FileUtil.splitFilePath(checkbox.getFile());
		return folders[folders.length-1];
	}
	
	/**
	 * Loads children files/directories of an underlying file, creates
	 * <code>FileTreeNodes</code> for them and stores them in the "children"
	 * attribute. At the end it sets the "loaded" to true to indicate that
	 * children have already been loaded
	 */
	private void loadChildren(){
		//Debug
		System.out.println(checkbox.getFile().getPath() + ": entering loadChildren method");
		
		//if children have been loaded, there is no point in loading them again
		if(loaded){
			return;
		}
		// Loads children files (actual Files from filesystem)
		File[] filesArray = checkbox.getFile().listFiles();

		// If checkbox hold real file (not directory), listFiles returns null
		// and therefore there is no need to continue - there will be no children
		if(filesArray == null){
			//Debug
			System.out.println("loadFiles(): listFiles() returned null(" +
					checkbox.getFile().getPath() + ")");
			loaded = true;
			return;
		}

		//Make an List from children files
		List<File> files = new ArrayList<File>();
		for(int i = 0; i < filesArray.length; i++){
			files.add(filesArray[i]);
			System.out.println(checkbox.getFile().getPath() + ": adding child: " + filesArray[i].getPath());
		}

		// removes leaf files from the collection and leaves only directories
		// if directoriesOnly is true
		if(directoriesOnly){
			//list of foldres only
			List<File> dirs = new ArrayList<File>();
			for(File f : files){
				if(f.isDirectory()){
					//to foldre list add only folders
					dirs.add(f);
				}
			}
			//retain only dirs in the children list
			files.retainAll(dirs);
		}

		//for each of the file in files
		for(File f : files){
			//create a new FileTreeNode
			FileTreeNode node = new FileTreeNode(f, this, directoriesOnly);
			
			System.out.println("creating FileTreeNode from file: " + f.getPath());
			
			// We need to project selection state of parent node(this) to
			// newly created node(children). It can be either selected or
			// unselected. It cannot be undecided, because when the loadChildren is 
			// called for the first time, the current parent node(this) is being 
			// expanded for the first time and therefore cannot have only some of
			// the nodes selected
			node.getFileCheckBox().setState(checkbox.getState());

			// Setting up the slaves for parent node(directory) to prapagate 
			// selection changes
			checkbox.addSlave(node.getFileCheckBox());
			children.add(node);
		}
		//children have been loaded
		loaded = true;
	}
	
	/**
	 * This is an implementation of a <code>FilenameFilter</code> interface
	 * to filter out files based on the directoriesOnly attribute.
	 */
	private final class NodeFilenameFilter implements FilenameFilter
	{
		/**
		 * Returns true if directoriesOnly is false, else returns true if the 
		 * specified name denotes a folder. Else returns false.
		 */
		@Override public boolean accept(File dir, String name){
			if(directoriesOnly){
				return !name.contains(".");
			}
			else{
				return true;
			}
		}
	}

	/**
	 * Class for Enumeration of children. 
	 */
	private class FileEnumeration implements Enumeration<FileTreeNode>
	{
		//index of the enumeration (for iterating over it)
		int currentIndex;
		
		/**
		 * Returns whether there are still some elements left.
		 */
		@Override public boolean hasMoreElements(){
			return currentIndex < children.size();
		}

		/**
		 * Returns next FileTreeNode in the children collection.
		 */
		@Override public FileTreeNode nextElement(){
			FileTreeNode f = children.get(currentIndex++);
			return f;
		}
	}
}
